// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: child_node_list.cpp 317 2009-01-12 11:10:14Z heavyzheng $
//

#ifndef ZZHENG_XML_SOURCE
#define ZZHENG_XML_SOURCE
#endif // ZZHENG_XML_SOURCE

#include <zzheng/xml/dom/libxml2/child_node_list.hpp>
#include <zzheng/xml/dom/libxml2/element.hpp>
#include <zzheng/xml/dom/libxml2/utility.hpp>
#include <zzheng/xml/dom/exception.hpp>

#include <libxml/tree.h>
#include <boost/numeric/conversion/cast.hpp>
#include <string>

namespace zzheng {
namespace xml {
namespace dom {
namespace libxml2_ {

    child_node_list::child_node_list(xmlNode* px): raw_(px) {
        assert(raw()->type == XML_ELEMENT_NODE);
    }

    child_node_list::~child_node_list() {
        raw_ = 0;
    }

    bool child_node_list::empty() const {
        return (first_() == 0);
    }

    size_type child_node_list::size() const {
        size_type count = 0;
        for (const_iterator i = begin(); i != end(); ++i) {
            ++count;
        }
        return count;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // iterators
    //

    child_node_list::iterator child_node_list::begin() {
        return iterator( const_cast<child_node*>(first_()) );
    }

    child_node_list::iterator child_node_list::end() {
        return iterator();
    }

    child_node_list::const_iterator child_node_list::begin() const {
        return const_iterator( first_() );
    }

    child_node_list::const_iterator child_node_list::end() const {
        return const_iterator();
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // element access
    //

    child_node& child_node_list::front() {
        return const_cast<child_node&>( (static_cast<const child_node_list&>(*this)).front() );
    }

    const child_node& child_node_list::front() const {
        if (empty()) {
            throw bad_dom_operation("call front() on an empty child_node_list.");
        } else {
            return *first_();
        }
    }

    child_node& child_node_list::back() {
        return const_cast<child_node&>( (static_cast<const child_node_list&>(*this)).back() );
    }

    const child_node& child_node_list::back() const {
        if (empty()) {
            throw bad_dom_operation("call back() on an empty child_node_list.");
        } else {
            basic_node_ptr<const child_node> last(first_());
            while (last->next_sibling() != 0) {
                last = last->next_sibling();
            }
            return *last;
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // modifiers/clear
    //

    void child_node_list::clear() {
        for (iterator i = begin(); i != end(); ) {
            i = erase(i);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // modifiers/push_*
    //

    basic_node_ptr<element> child_node_list::push_back(const element_proxy& child) {
        xmlNode* px = insert_(end(), create_node_(child));
        basic_node_ptr<element> pushed( static_cast<element*>(px->_private) );
        pushed->set_uri(child.uri());
        return pushed;
    }

    basic_node_ptr<text> child_node_list::push_back(const text_proxy& child) {
        xmlNode* px = insert_(end(), create_node_(child));
        return basic_node_ptr<text>( static_cast<text*>(px->_private) );
    }

    basic_node_ptr<comment> child_node_list::push_back(const comment_proxy& child) {
        xmlNode* px = insert_(end(), create_node_(child));
        return basic_node_ptr<comment>( static_cast<comment*>(px->_private) );
    }

    void child_node_list::push_back(const child_node& child) {
        xmlNode* px = child.clone_raw(true); // recursive clone: never returns null.
        px = insert_(end(), px);
        assert(px != 0);
    }

    void child_node_list::push_back(child_node& child, adopt_or_clone_t aoc) {
        xmlNode* px = ( aoc == adopt_node ? child.raw() : child.clone_raw(true) );
        px = insert_(end(), px);
        assert(px != 0);
    }

    basic_node_ptr<element> child_node_list::push_front(const element_proxy& child) {
        xmlNode* px = insert_(begin(), create_node_(child));
        basic_node_ptr<element> pushed( static_cast<element*>(px->_private) );
        pushed->set_uri(child.uri());
        return pushed;
    }

    basic_node_ptr<text> child_node_list::push_front(const text_proxy& child) {
        xmlNode* px = insert_(begin(), create_node_(child));
        return basic_node_ptr<text>( static_cast<text*>(px->_private) );
    }

    basic_node_ptr<comment> child_node_list::push_front(const comment_proxy& child) {
        xmlNode* px = insert_(begin(), create_node_(child));
        return basic_node_ptr<comment>( static_cast<comment*>(px->_private) );
    }

    void child_node_list::push_front(const child_node& child) {
        xmlNode* px = child.clone_raw(true); // recursive clone: never returns null.
        px = insert_(begin(), px);
        assert(px != 0);
    }

    void child_node_list::push_front(child_node& child, adopt_or_clone_t aoc) {
        xmlNode* px = ( aoc == adopt_node ? child.raw() : child.clone_raw(true) );
        px = insert_(begin(), px);
        assert(px != 0);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // modifiers/pop_*
    //

    void child_node_list::pop_back() {
        // Check the list is not empty.
        if (empty()) {
            throw bad_dom_operation("Couldn't perform pop back on an empty list.");
        }
        // Find the last child node in the list (before-the-end iterator).
        iterator last;
        for (iterator i = begin(); i != end(); ++i) {
            last = i;
        }
        // Erase the last child node from the list.
        erase(last);
    }

    void child_node_list::pop_front() {
        // Check the list is not empty.
        if (empty()) {
            throw bad_dom_operation("Couldn't perform pop front on an empty list.");
        }
        // Erase the first child node from the list.
        erase(begin());
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // modifiers/insert
    //

    basic_node_ptr<element> child_node_list::insert(iterator pos, const element_proxy& child) {
        xmlNode* px = insert_(pos, create_node_(child));
        basic_node_ptr<element> inserted( static_cast<element*>(px->_private) );
        inserted->set_uri(child.uri());
        return inserted;
    }

    basic_node_ptr<text> child_node_list::insert(iterator pos, const text_proxy& child) {
        xmlNode* px = insert_(pos, create_node_(child));
        return basic_node_ptr<text>( static_cast<text*>(px->_private) );
    }

    basic_node_ptr<comment> child_node_list::insert(iterator pos, const comment_proxy& child) {
        xmlNode* px = insert_(pos, create_node_(child));
        return basic_node_ptr<comment>( static_cast<comment*>(px->_private) );
    }

    child_node_list::iterator child_node_list::insert(iterator pos, const child_node& child) {
        xmlNode* px = child.clone_raw(true); // recursive clone: never returns null.
        px = insert_(pos, px);
        return iterator( static_cast<child_node*>(px->_private) );
    }

    child_node_list::iterator child_node_list::insert(iterator pos,
                                                      child_node& child,
                                                      adopt_or_clone_t aoc) {
        xmlNode* px = ( aoc == adopt_node ? child.raw() : child.clone_raw(true) );
        px = insert_(pos, px);
        return iterator( static_cast<child_node*>(px->_private) );
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // modifiers/erase
    //

    child_node_list::iterator child_node_list::erase(iterator pos) {
        // Check the iterator to erase is not the end and belongs to this list.
        check_ownership_(pos);
        if (pos == end()) {
            throw bad_dom_operation("The iterator to erase should not point to the end.");
        }
        // Unlink and free the libxml2 node pointed by the iterator.
        xmlNode* px = pos->raw();
        assert(px != 0);
        ++pos;
        xmlUnlinkNode(px);
        xmlFreeNode(px);
        return pos;
    }

    child_node_list::iterator child_node_list::erase(iterator first, iterator last) {
        // Check iterator parameters.
        check_ownership_(first);
        check_ownership_(last);
        // Erase all the child nodes between the two iterators.
        for (iterator i = first; i != last; ) {
            i = erase(i);
        }
        return last;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // private
    //

    element& child_node_list::owner() {
        return const_cast<element&>( (static_cast<const child_node_list&>(*this)).owner() );
    }

    const element& child_node_list::owner() const {
        const element* owner = static_cast<const element*>(raw()->_private);
        assert(owner != 0 && "Owner element should not be null.");
        return *owner;
    }

    const child_node* child_node_list::first_() const {
        if (raw()->children != 0) {
            return static_cast<const child_node*>(raw()->children->_private);
        } else {
            return 0;
        }
    }

    void child_node_list::check_ownership_(iterator i) {
        if ( i != end() && (i->parent() == 0 || i->parent()->raw() != owner().raw()) ) {
            throw bad_dom_operation("The iterator does not belong to this list.");
        }
    }

    xmlNode* child_node_list::insert_(iterator pos, xmlNode* child) {
        // Check the ownership of the iterator parameter.
        check_ownership_(pos);
        // Insert the libxml2 node before the specified position.
        xmlNode* px = 0;
        if (pos == end()) {
            px = xmlAddChild(raw(), child);
        } else {
            px = xmlAddPrevSibling(pos->raw(), child);
        }
        assert(px != 0 && "xmlAddChild() or xmlAddPrevSibling() should not return null.");
        if (px == 0) {
            std::string errmsg = "Invalid return value of xmlAddChild() or xmlAddPrevSibling().";
            throw internal_dom_error(errmsg.c_str());
        }
        // Return the inserted libxml2 node.
        return px;
    }

    xmlNode* child_node_list::create_node_(const element_proxy& child) {
        xmlNode* px = xmlNewDocNode( raw()->doc, 0, to_xml_chars(child.name().c_str()), 0 );
        if (px == 0) {
            std::string errmsg = "Fail to create libxml2 element node for " + child.str();
            throw internal_dom_error(errmsg.c_str());
        }
        return px;
    }

    xmlNode* child_node_list::create_node_(const text_proxy& child) {
        xmlNode* px = 0;
        if (child.is_cdata()) {
            px = xmlNewCDataBlock( raw()->doc,
                                   to_xml_chars(child.value().c_str()),
                                   boost::numeric_cast<int>(child.value().size()) );
        } else {
            px = xmlNewDocText( raw()->doc,
                                to_xml_chars(child.value().c_str()) );
        }
        if (px == 0) {
            std::string errmsg = "Fail to create libxml2 text node for " + child.str();
            throw internal_dom_error(errmsg.c_str());
        } else {
            return px;
        }
    }

    xmlNode* child_node_list::create_node_(const comment_proxy& child) {
        xmlNode* px = xmlNewDocComment( raw()->doc,
                                        to_xml_chars(child.value().c_str()) );
        if (px == 0) {
            std::string errmsg = "Fail to create libxml2 comment node " + child.str();
            throw internal_dom_error(errmsg.c_str());
        } else {
            return px;
        }
    }

} // namespace zzheng::xml::dom::libxml2_
} // namespace zzheng::xml::dom
} // namespace zzheng::xml
} // namespace zzheng




